package com.naiterui.ehp.bs.consult.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.google.common.collect.Lists;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.common.repository.page.PageParamVO;
import com.naiterui.common.repository.page.PageVO;
import com.naiterui.ehp.bp.bo.consult.VideoConsultBO;
import com.naiterui.ehp.bp.bo.consult.VideoConsultRecordBO;
import com.naiterui.ehp.bp.bo.doctor.RecomDoctorBO;
import com.naiterui.ehp.bp.bo.doctor.RecomPatientBO;
import com.naiterui.ehp.bp.bo.doctor.RecomUserInfoBO;
import com.naiterui.ehp.bp.bo.groupConsult.GroupConsultPreSignBO;
import com.naiterui.ehp.bp.bo.groupConsult.GroupConsultPreSignParamBO;
import com.naiterui.ehp.bp.bo.groupConsult.GroupConsultSignCallbackBO;
import com.naiterui.ehp.bp.bo.groupConsult.GroupConsultSignParamBO;
import com.naiterui.ehp.bp.bo.patient.MsgMaPublishParamBO;
import com.naiterui.ehp.bp.bo.patient.PatientInquirerBO;
import com.naiterui.ehp.bp.bo.recommend.RecomPrescriptionBO;
import com.naiterui.ehp.bp.bo.statistics.AsConsultRecordBO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.DrSystemConfEnum;
import com.naiterui.ehp.bp.constants.PayStatus;
import com.naiterui.ehp.bp.domain.Department;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.DrSystemConf;
import com.naiterui.ehp.bp.domain.DrVideoConsult;
import com.naiterui.ehp.bp.domain.DrVideoGroupConsult;
import com.naiterui.ehp.bp.domain.Hospital;
import com.naiterui.ehp.bp.domain.Patient;
import com.naiterui.ehp.bp.domain.PatientInquirerInfo;
import com.naiterui.ehp.bp.domain.VideoConsultMessage;
import com.naiterui.ehp.bp.support.exception.BaseExceptionCodes;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.utils.JsonMapper;
import com.naiterui.ehp.bp.utils.beans.BeanUtil;
import com.naiterui.ehp.bp.utils.biz.TitleUtil;
import com.naiterui.ehp.bp.utils.consult.VideoConsultUtil;
import com.naiterui.ehp.bp.utils.encypt.DESUtil;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.notice.AppNoticeUtil;
import com.naiterui.ehp.bp.utils.payment.OrderUtils;
import com.naiterui.ehp.bp.utils.payment.bo.RefundRequestBO;
import com.naiterui.ehp.bp.utils.thread.ThreadPoolUtil;
import com.naiterui.ehp.bs.consult.constant.VideoConsultConstant;
import com.naiterui.ehp.bs.consult.dto.VideoConsultDTO;
import com.naiterui.ehp.bs.consult.exception.ExceptionCodes;
import com.naiterui.ehp.bs.consult.feign.AdFeginClient;
import com.naiterui.ehp.bs.consult.feign.ApFeignClient;
import com.naiterui.ehp.bs.consult.feign.ESignFeignClient;
import com.naiterui.ehp.bs.consult.feign.EmrFeignClient;
import com.naiterui.ehp.bs.consult.feign.RecommendFeignClient;
import com.naiterui.ehp.bs.consult.feign.SearchFeignClient;
import com.naiterui.ehp.bs.consult.repository.ApDepartmentRepository;
import com.naiterui.ehp.bs.consult.repository.ApHospitalRepository;
import com.naiterui.ehp.bs.consult.repository.DoctorRepository;
import com.naiterui.ehp.bs.consult.repository.DrGroupConsultRepository;
import com.naiterui.ehp.bs.consult.repository.DrSystemConfRepository;
import com.naiterui.ehp.bs.consult.repository.DrVideoConsultRepository;
import com.naiterui.ehp.bs.consult.repository.PatientInquirerInfoRepository;
import com.naiterui.ehp.bs.consult.repository.PatientRepository;
import com.naiterui.ehp.bs.consult.repository.VideoConsultMessageRepository;
import com.naiterui.ehp.bs.consult.service.IVideoConsultService;
import com.naiterui.ehp.bs.consult.util.ImUtil;
import com.naiterui.ehp.bs.consult.util.ImageUrlUtil;
import com.naiterui.ehp.bs.consult.util.TrtcUtil;
import com.naiterui.ehp.bs.consult.util.UploadUtil;
import com.naiterui.ehp.bs.consult.vo.DoctorConsultDetailVO;
import com.naiterui.ehp.bs.consult.vo.DoctorConsultListVO;
import com.naiterui.ehp.bs.consult.vo.DoctorVO;
import com.naiterui.ehp.bs.consult.vo.GroupConsultApplyVO;
import com.naiterui.ehp.bs.consult.vo.GroupConsultListVO;
import com.naiterui.ehp.bs.consult.vo.GroupConsultOpinionConfirmVO;
import com.naiterui.ehp.bs.consult.vo.GroupConsultOpinionVO;
import com.naiterui.ehp.bs.consult.vo.PatientConsultRecordVO;
import com.naiterui.ehp.bs.consult.vo.RefusalReasonVO;
import com.naiterui.ehp.bs.consult.vo.UserInfoVO;
import com.naiterui.ehp.bs.consult.vo.VideoConsultCacheVO;
import com.naiterui.ehp.bs.consult.vo.VideoConsultConfigVO;
import com.naiterui.ehp.bs.consult.vo.VideoConsultMessageListVO;
import com.naiterui.ehp.bs.consult.vo.VideoConsultRoomConfigVO;
import com.tencentcloudapi.trtc.v20190722.models.DismissRoomResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.naiterui.ehp.bs.consult.constant.VideoConsultConstant.GROUP_CONSULT_CACHE_KEY;

/**
 * 视频问诊
 *
 * @author yangfuya
 * @version 1.0
 * @date 2021-02-25 18:50
 **/
@Slf4j
@RequiredArgsConstructor
@Service
public class VideoConsultServiceImpl implements IVideoConsultService {

    @Value("${txcloud.im.appId}")
    private String appId;
    @Value("${txcloud.im.key}")
    private String key;
    @Value("${file.base.prefix}")
    private String basePath;
    @Value("${ehp.video-consult-path}")
    private String videoConsultPath;
    @Value("${ehp.domain.img}")
    private String imgHost;
    @Value("${wx.miniapp.appid}")
    private String wxMiniappAppid;
    @Value("${ehp.domain.ap}")
    private String PATIENT_URL;
    @Value("${wx.thumbMediaId}")
    private String thumbMediaId;

    // amp服务地址
    final String ampHost = ConfigUtil.getString("amp.host.url");
    // 患者联系客服地址
    final String patientCustomerServiceUrl = ConfigUtil.getString("patient.customer.service.url");
    final String basehospitalName = ConfigUtil.getString("ehp.hospital.name");

    private final DrVideoConsultRepository videoConsultRepository;
    private final DrGroupConsultRepository groupConsultRepository;
    private final VideoConsultMessageRepository videoConsultMessageRepository;
    private final DoctorRepository doctorRepository;
    private final PatientRepository patientRepository;
    private final PatientInquirerInfoRepository patientInquirerInfoRepository;
    private final ApHospitalRepository hospitalRepository;
    private final ApDepartmentRepository departmentRepository;
    private final DrSystemConfRepository systemConfRepository;

    private final ApFeignClient apFeignClient;
    private final AdFeginClient adFeignClient;
    private final EmrFeignClient emrFeignClient;
    private final RecommendFeignClient recommendFeignClient;
    private final SearchFeignClient searchFeignClient;

    private final ESignFeignClient eSignFeignClient;

    @Override
    public VideoConsultBO getVideoConsult(Long patientId, Long doctorId) {
        DrVideoConsult videoConsult = videoConsultRepository.findFirstByPatientIdAndDoctorIdOrderByCreatedAtDesc(patientId, doctorId);
        return getVideoConsultBO(videoConsult);
    }

    @Override
    public List<VideoConsultBO> getVideoConsults(List<Long> videoConsultIds) {
        Iterable<DrVideoConsult> videoConsultIterable = videoConsultRepository.findAllById(videoConsultIds);
        List<VideoConsultBO> result = new ArrayList<>();
        videoConsultIterable.forEach(drVideoConsult -> result.add(getVideoConsultBO(drVideoConsult)));
        return result;
    }

    @Override
    public VideoConsultBO getVideoConsultByOrderSn(String orderSn) {
        DrVideoConsult videoConsult = videoConsultRepository.findFirstByOrderSn(orderSn);
        VideoConsultBO videoConsultBO = getVideoConsultBO(videoConsult);
        return videoConsultBO;
    }

    @Override
    public VideoConsultBO getVideoConsultById(Long videoConsultId) {
        DrVideoConsult videoConsult = videoConsultRepository.get(videoConsultId);
        VideoConsultBO videoConsultBO = getVideoConsultBO(videoConsult);
        return videoConsultBO;
    }

    @Override
    @Transactional
    public VideoConsultBO saveOrUpdate(VideoConsultBO videoConsultBO) {
        DrVideoConsult videoConsult = null;
        if (videoConsultBO.getId() != null) {
            videoConsult = videoConsultRepository.get(videoConsultBO.getId());
        }
        if (videoConsult == null) {
            videoConsult = new DrVideoConsult();
            videoConsult.setDoctorId(videoConsultBO.getDoctorId());
            videoConsult.setPatientId(videoConsultBO.getPatientId());
            videoConsult.setInquirerId(videoConsultBO.getInquirerId());
            videoConsult.setOrderSn(videoConsultBO.getOrderSn());
            videoConsult.setRoomId(VideoConsultUtil.getRoomId());
            videoConsult.setCreatedAt(new Date());
            videoConsult.setIsTreat(1);
            videoConsult.setConditionDesc(videoConsultBO.getConditionDesc());
            videoConsult.setOfflineDiagnosis(videoConsultBO.getOfflineDiagnosis());
        }
        videoConsult.setChangedAt(new Date());
        if (videoConsultBO.getPayStatus() != null) {
            videoConsult.setPayStatus(videoConsultBO.getPayStatus());
        }
        if (videoConsultBO.getVideoConsultStatus() != null) {
            videoConsult.setVideoConsultStatus(videoConsultBO.getVideoConsultStatus());
        }
        if (videoConsultBO.getPrice() != null) {
            videoConsult.setPrice(videoConsultBO.getPrice());
        }
        if (videoConsultBO.getConsultTime() != null) {
            videoConsult.setConsultTime(videoConsultBO.getConsultTime());
        }
        if (videoConsultBO.getIsTreat() != null) {
            videoConsult.setIsTreat(videoConsultBO.getIsTreat());
        }
        DrVideoConsult saveOrUpdate = videoConsultRepository.saveOrUpdate(videoConsult);
        videoConsultBO.setId(saveOrUpdate.getId());
        videoConsultBO.setFinish(DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE == videoConsult.getVideoConsultStatus());
        videoConsultBO.setRoomId(saveOrUpdate.getRoomId());
        return videoConsultBO;
    }

    @Override
    public List<VideoConsultMessageListVO> accept(VideoConsultDTO videoConsultDTO) throws BusinessException {
        Long videoConsultId = videoConsultDTO.getVideoConsultId();
        return inRoomVideoConsultMessages(videoConsultId, true);
    }

    @Override
    public VideoConsultConfigVO getAppIdAndKey(Long videoConsultId, String userId) throws BusinessException {
        if (null != videoConsultId) {
            DrVideoConsult drVideoConsult = this.videoConsultRepository.get(videoConsultId);
            if (null == drVideoConsult) {
                throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
            }
            if (DrVideoConsult.VIDEO_CONSULT_PAY_SUCCESS != drVideoConsult.getPayStatus()) {
                throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_PAY, "视频问诊未支付");
            }
            Integer videoConsultStatus = drVideoConsult.getVideoConsultStatus();
            if(userId.contains("patient-") && DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT == videoConsultStatus){
                throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_ACCEPT, "医生未接诊");
            }
            if (DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL == videoConsultStatus
                    || DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE == videoConsultStatus) {
                throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_IS_OVER, "视频问诊已结束");
            }
        }
        return VideoConsultConfigVO.builder().appId(appId).key(key).userId(userId).userSig(ImUtil.genUserSig(userId))
            .build();
    }

    @Override
    public String uploadFile(String roomId, MultipartFile file) {
        String path = String.format(videoConsultPath, roomId);
        try {
            return UploadUtil.uploadFile(basePath, path, file);
        } catch (IOException e) {
            return "";
        }
    }

    @Override
    public void sendGroupMsg(Integer roomId, Integer isDoctorSend, String userId, MultipartFile file) throws BusinessException {
        String path = String.format(videoConsultPath, roomId);
        String imageUrl;
        try {
            imageUrl = UploadUtil.uploadFile(basePath, path, file);
        } catch (IOException e) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_FILE_UPLOAD_FAILED, "视频问诊图片上传失败");
        }
        DrVideoConsult videoConsult = videoConsultRepository.findByRoomId(roomId);
        if (null == videoConsult) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_ROOM_NOT_EXIST, "该房间不存在");
        }
        JSONObject content = new JSONObject();
        content.put("roomId", videoConsult.getRoomId());
        content.put("path", imageUrl);
        content.put("isDoctorSend", isDoctorSend == 1 ? true : false);
        content.put("doctorName", "");
        if (isDoctorSend == 1) {
            Long doctorId = VideoConsultUtil.getId(userId);
            Doctor doctor = this.doctorRepository.get(doctorId);
            content.put("doctorName", Optional.ofNullable(doctor).map(Doctor::getName).orElse(""));
        }
        PatientInquirerInfo patientInquirerInfo = this.patientInquirerInfoRepository.get(videoConsult.getInquirerId());
        content.put("patientName", patientInquirerInfo.getName());

        VideoConsultMessage videoConsultMessage = new VideoConsultMessage();
        videoConsultMessage.setVideoConsultId(videoConsult.getId());
        videoConsultMessage.setDoctorId(videoConsult.getDoctorId());
        videoConsultMessage.setPatientId(videoConsult.getPatientId());
        videoConsultMessage.setInquirerId(videoConsult.getInquirerId());
        videoConsultMessage.setType(VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_IMAGE);
        videoConsultMessage.setContent(content.toJSONString());
        videoConsultMessage.setCreatedAt(new Date());
        videoConsultMessage.setCreatedBy("system");
        videoConsultMessage = this.videoConsultMessageRepository.save(videoConsultMessage);
        content.put("id", videoConsultMessage.getId());
        content.put("path", imgHost + imageUrl);

        // 发送图片使用自有im
        AppNoticeUtil.VideoConsultImage noticeInfo = new AppNoticeUtil.VideoConsultImage(content.toJSONString());
        try {
            // 发消息前检查医生和患者是否在房间
            VideoConsultCacheVO cacheVO =
                JSON.parseObject(RedisUtil.valueOps().getString(VideoConsultConstant.VIDEO_CONSULT_CACHE_KEY + roomId),
                    VideoConsultCacheVO.class);
            Integer status = cacheVO.getStatus();
            // 给医生发消息
            if (VideoConsultCacheVO.DOCTOR_APP_EXIST == status || VideoConsultCacheVO.DOCTOR_PC_EXIST == status
                || VideoConsultCacheVO.DOCTOR_APP_AND_PATIENT_EXIST == status
                || VideoConsultCacheVO.DOCTOR_PC_AND_PATIENT_EXIST == status) {
                AppNoticeUtil.sendSingleNotice(ampHost, videoConsult.getDoctorId(),
                    AppNoticeUtil.NoticeType.DR_VIDEO_CONSULT_IMAGE, noticeInfo);
            }
            // 给患者发消息的时候不判断在不在房间，因为小程序发送图片的时候会断开连接
            AppNoticeUtil.sendSingleNotice(ampHost, videoConsult.getPatientId(),
                    AppNoticeUtil.NoticeType.PT_VIDEO_CONSULT_IMAGE, noticeInfo);
            // 给会诊专家发送消息
            Set<String> smembers = RedisUtil.setOps()
                .smembers(VideoConsultConstant.VIDEO_CONSULT_SPECIALIST_CACHE_KEY + videoConsult.getRoomId());
            if (CollUtil.isNotEmpty(smembers)) {
                for (String smember : smembers) {
                    AppNoticeUtil.sendSingleNotice(ampHost, Long.parseLong(smember),
                        AppNoticeUtil.NoticeType.DR_VIDEO_CONSULT_IMAGE, noticeInfo);
                }
            }
        } catch (BusinessException e) {
            log.error("发送视频问诊图片时出错", e);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void finish(Integer roomId, String diagnosis) {
        boolean existsKey = RedisUtil.keyOps().existsKey(VideoConsultConstant.VIDEO_CONSULT_CACHE_KEY + roomId);
        if (!existsKey) {
            // String destroyGroup = ImUtil.destroyGroup(String.valueOf(roomId));
            // log.info("destroy group result:{}", destroyGroup);
            DismissRoomResponse dismissRoom = TrtcUtil.dismissRoom(roomId.longValue());
            log.info("dismiss room result:{}", JSONUtil.toJsonStr(dismissRoom));
            return;
        }
        String cacheStr = RedisUtil.valueOps().getString(VideoConsultConstant.VIDEO_CONSULT_CACHE_KEY + roomId);
        VideoConsultCacheVO cacheVO = JSON.parseObject(cacheStr, VideoConsultCacheVO.class);
        Long talkTime = null;
        if (null != cacheVO.getStartTime()) {
            talkTime = (System.currentTimeMillis() - cacheVO.getStartTime()) / 1000;
        }
        RedisUtil.keyOps().delete(VideoConsultConstant.VIDEO_CONSULT_CACHE_KEY + roomId);
        DrVideoConsult videoConsult = this.videoConsultRepository.findByRoomId(roomId);
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE == videoConsult.getVideoConsultStatus()) {
            return;
        }
        Long videoConsultId = videoConsult.getId();
        Long patientId = videoConsult.getPatientId();
        Long doctorId = videoConsult.getDoctorId();
        RedisUtil.keyOps().delete(getVideoSessionKey(doctorId, patientId));
        if (null != talkTime) {
            videoConsult.setTalkTime(talkTime);
        }
        videoConsult.setVideoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE);
        videoConsult.setChangedAt(new Date());
        videoConsult.setDiagnosis(StringUtils.isEmpty(diagnosis) ? null : diagnosis);
        this.videoConsultRepository.update(videoConsult);
        /*
         * 视频问诊结束，异步推送患者评价、投诉通知
         */
        RecomUserInfoBO recomUserInfo = adFeignClient.getUserInfoForRecom(doctorId, patientId);
        RecomDoctorBO doctor = recomUserInfo.getDoctor();
        RecomPatientBO patient = recomUserInfo.getPatient();
        if (null != doctor && null != patient) {
            String content = "与" + doctor.getName() + "医生的视频问诊已结束。如有需要请再次向医生咨询~";
            String path = "/pages/consult/record/index?doctorId="+doctorId+"&type=1";
            this.sendVideoConsultMaMsg(videoConsultId, doctorId, patientId, path, content,
                videoConsult.getConditionDesc());
           /* Map<String, Object> paramMap = new HashMap<>();
            paramMap.put("doctorId", doctorId);
            paramMap.put("content", content);
            paramMap.put("patientId", patientId);
            paramMap.put("consultSessionId", videoConsultId);
            paramMap.put("consultType", CommonConstant.ConsultType.VIDEO);
            apFeignClient.patientPush(ApFeignClient.PATIENT_PUSH_PLATFORMID, ApFeignClient.PATIENT_PUSH_TEMPLATED_SESSION_FINISH, JSON.toJSONString(paramMap));*/
        } else {
            log.error("结束视频问诊异常：医生或患者不存在：doctorId {},patientId {}", doctorId, patientId);
        }
        // String destroyGroup = ImUtil.destroyGroup(String.valueOf(roomId));
        // log.info("destroy group result:{}", destroyGroup);
        DismissRoomResponse dismissRoom = TrtcUtil.dismissRoom(roomId.longValue());
        log.info("dismiss room result:{}", JSONUtil.toJsonStr(dismissRoom));

        // 异步同步问诊数据到统计
        ThreadPoolUtil.execute(() -> {
            AsConsultRecordBO consultRecordBO = new AsConsultRecordBO();
            consultRecordBO.setDoctorId(doctorId);
            consultRecordBO.setPatientId(patientId);
            consultRecordBO.setConsultType(AsConsultRecordBO.TYPE_VIDEO);
            consultRecordBO.setConsultId(videoConsult.getId());
            consultRecordBO.setConsultPrice(videoConsult.getPrice());
            consultRecordBO.setGenerateDate(new Date());
            consultRecordBO.setCreatedAt(new Date());
            consultRecordBO.setCreatedBy("system");
            log.info("问诊结束同步es：", JsonMapper.toJson(consultRecordBO));
            searchFeignClient.createConsultRecordIndex(consultRecordBO);
        });
    }

    @Override
    public void refuse(VideoConsultDTO videoConsultDTO) throws BusinessException {
        Long videoConsultId = videoConsultDTO.getVideoConsultId();
        DrVideoConsult drVideoConsult = this.videoConsultRepository.get(videoConsultId);
        if (null == drVideoConsult) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        drVideoConsult.setRefusalReasonId(videoConsultDTO.getRefusalReasonId());
        drVideoConsult.setRefusalReason(videoConsultDTO.getRefusalReason());
        drVideoConsult.setChangedAt(new Date());
        videoConsultRepository.update(drVideoConsult);
        // 拒诊退款
        cancelVideoConsult(videoConsultId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelVideoConsult(Long videoConsultId) throws BusinessException {
        log.info("取消视频咨询 videoConsultId:{}", videoConsultId);
        DrVideoConsult videoConsult = videoConsultRepository.get(videoConsultId);
        if (videoConsult == null) {
            log.error("未找到该视频咨询订单 videoCOnsultId:{}", videoConsult);
            return;
        }
        if (videoConsult.getVideoConsultStatus() != DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT) {
            log.info("当前视频咨询状态不是为待接诊状态 videoConsultId:{},videoConsultStatus:{}", videoConsultId, videoConsult.getVideoConsultStatus());
            return;
        }
        // 结束当前视频咨询
        RedisUtil.keyOps().delete(getVideoSessionKey(videoConsult.getDoctorId(), videoConsult.getPatientId()));
        // 给医生端发送待接诊消息
        AppNoticeUtil.VideoConsult noticeInfo = new AppNoticeUtil.VideoConsult(AppNoticeUtil.VideoConsult.OPERATE_REDUCE);
        try {
            AppNoticeUtil.sendSingleNotice(ampHost, videoConsult.getDoctorId(), AppNoticeUtil.NoticeType.DR_VIDEO_CONSULT, noticeInfo);
        } catch (BusinessException e) {
            log.error("发送医生视频咨询消息失败", e);
        }
        this.sendVideoConsultMaMsg(videoConsultId, videoConsult.getDoctorId(), videoConsult.getPatientId(),
                "/pages/consult/record/index?type=2&doctorId=" + videoConsult.getDoctorId(),
            "医生未接诊您的视频申请，点击查看原因", videoConsult.getConditionDesc());
        // 发送患者消息
//        sendSystemMsg(videoConsult.getPatientId(), "医生正忙（" + getRefusalReason(videoConsult.getRefusalReasonId()) + "）" +
//                "，已取消您的视频问诊，您已支付的费用会在10个工作日内原路退还到您的账户，请注意查收\r\n------------------------------" +
//                "\r\n<a href='" + patientCustomerServiceUrl + "'>【联系客服】</a>");
        // 如果视频咨询为零元，修改状态即可
        if (videoConsult.getPrice() <= 0) {
            videoConsult.setPayStatus(PayStatus.REFUND.getValue());
            videoConsult.setVideoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL);
            videoConsult.setIsTreat(0);
            videoConsult.setChangedAt(new Date());
            videoConsultRepository.update(videoConsult);
            return;
        }
        // 调用微信退款
        OrderUtils.refund(RefundRequestBO.builder()
                // 订单号
                .orderSn(videoConsult.getOrderSn())
                .refundOrderSn(videoConsult.getOrderSn() + "RE")
                // 订单金额
                .totalFee(videoConsult.getPrice())
                // 退款金额
                .refundFee(videoConsult.getPrice())
                // 业务回调地址
                .notifyBusinessUrl(PATIENT_URL + "/inner/notify/refund").build());
    }

    public String getRefusalReason(Long refusalReasonId) {
        // 拒诊原因
        String rejectedReason = "未接诊";
        if (refusalReasonId != null) {
            DrSystemConf drSystemConf = systemConfRepository.get(refusalReasonId);
            if (drSystemConf != null) {
                rejectedReason = drSystemConf.getConfValue();
            }
        }
        return rejectedReason;
    }

    @Override
    public PageVO<PatientConsultRecordVO> patientConsultRecord(Long patientId, Long doctorId, PageParamVO pageParam) {
        ArrayList<Integer> status = ListUtil.toList(DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT,DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING, DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE,DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL,DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE);
        Page<DrVideoConsult> videoConsultPage = videoConsultRepository.findByPatientIdAndDoctorIdAndVideoConsultStatusInOrderByChangedAtDesc(patientId, doctorId, status, pageParam.getPageable());
        if (CollectionUtils.isEmpty(videoConsultPage.getContent())) {
            return new PageVO<>();
        }
        List<DrVideoConsult> drVideoConsults = videoConsultPage.getContent();
        Set<Long> doctorIds = drVideoConsults.stream().map(DrVideoConsult::getDoctorId).collect(Collectors.toSet());
        List<Doctor> doctors = doctorRepository.findByIdIn(doctorIds);
        Map<Long, DoctorVO> doctorInfo = getDoctorInfo(doctors);
        return PageVO.newBuilder()
                .pageNo(pageParam.getPage())
                .pageSize(pageParam.getNum())
                .totalCount((int) videoConsultPage.getTotalElements())
                .result(drVideoConsults.stream().map(videoConsult ->
                        PatientConsultRecordVO.builder()
                                .consultTime(videoConsult.getChangedAt())
                                .department(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getDepartmentName()).orElse(""))
                                .hospital(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getHospitalName()).orElse(""))
                                .name(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getName()).orElse(""))
                                .title(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getTitle()).orElse(""))
                                .photo(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getPhoto()).orElse(""))
                                .talkTime(Optional.ofNullable(videoConsult.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue())).orElse("--:--:--"))
                                .roomId(videoConsult.getRoomId())
                                .videoConsultId(videoConsult.getId())
                                .videoConsultStatus(videoConsult.getVideoConsultStatus())
                // .refusalReason(videoConsult.getRefusalReasonId() != null
                // ? getRefusalReason(videoConsult.getRefusalReasonId()) : videoConsult.getRefusalReason())
                .refusalReason(videoConsult.getRefusalReason())
                                .build()
                ).collect(Collectors.toList()))
                .build();
    }

    public Map<Long, DoctorVO> getDoctorInfo(List<Doctor> doctors) {
        // 获取科室id集合
        Set<Integer> departmentIds = doctors.stream().map(Doctor::getDepartmentId).collect(Collectors.toSet());
        // 获取医院id集合
        Set<Integer> hospitalIds = doctors.stream().map(Doctor::getHospitalId).collect(Collectors.toSet());
        // 根据id批量查询科室
        List<Department> departments = departmentRepository.findByIdIn(departmentIds);
        // 根据id批量查询医院
        List<Hospital> hospitals = hospitalRepository.findByIdIn(hospitalIds);
        Map<Integer, String> departmentNameMap = departments.stream().collect(Collectors.toMap(Department::getId, Department::getName));
        Map<Integer, String> hospitalNameMap = hospitals.stream().collect(Collectors.toMap(Hospital::getId, Hospital::getName));
        Map<Long, DoctorVO> resMap = new HashMap<>();
        for (Doctor doctor : doctors) {
            String departmentName = departmentNameMap.get(doctor.getDepartmentId());
            String hospitalName =
                doctor.getHospitalId() == 0 ? doctor.getHospitalName() : hospitalNameMap.get(doctor.getHospitalId());
            String name = doctor.getName();
            String title = TitleUtil.getTitle(doctor.getTitleId());
            resMap.put(doctor.getId(), DoctorVO.builder()
                    .title(title)
                    .departmentName(departmentName)
                    .hospitalName(hospitalName)
                    .name(name)
                .photo(ImageUrlUtil.getImagePath(doctor.getHeadUrl()))
                    .build());
        }
        return resMap;
    }

    /**
     * 发送客服消息
     *
     * @param patientId 患者id
     * @param msg       消息
     */
    private void sendSystemMsg(Long patientId, String msg) {
        Patient patient = this.patientRepository.get(patientId);
        this.apFeignClient.sendSystemMsg(patient.getOpenId(), msg);
    }

    /**
     * 发送客服消息
     *
     * @param patientId 患者id
     */
    private void sendMiniProgramMsg(Long patientId, String title, String appId, String pagePath, String thumbMediaId) {
        Patient patient = this.patientRepository.get(patientId);
        this.apFeignClient.sendMiniProgramMsg(patient.getOpenId(), title, appId, pagePath, thumbMediaId);
    }


    @Override
    public PageVO<DoctorConsultListVO> doctorConsultList(Long doctorId, Integer status, Pageable pageable) throws BusinessException {
        List<Integer> videoConsultStatus = ListUtil.toList(DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT, DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING,
                DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE, DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL, DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE);
        if (status != null) {
            if (status == 1) {
                videoConsultStatus = ListUtil.toList(DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT,
                    DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING, DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE);
            } else if (status == 5) {
                videoConsultStatus = ListUtil.toList(DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL,
                    DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE);
            }
        }
        Page<DrVideoConsult> videoConsultPage = videoConsultRepository.findByDoctorIdAndVideoConsultStatusInOrderByIsTreatDescChangedAtDesc(doctorId, videoConsultStatus, pageable);
        if (CollectionUtils.isEmpty(videoConsultPage.getContent())) {
            return new PageVO<>();
        }
        List<DrVideoConsult> videoConsults = videoConsultPage.getContent();
        // 查询患者信息
        Set<Long> patientIds = videoConsults.stream().map(DrVideoConsult::getPatientId).collect(Collectors.toSet());
        List<Patient> patients = patientRepository.findByIdIn(patientIds);
        Map<Long, Patient> patientMap = patients.stream().collect(Collectors.toMap(Patient::getId, p -> p));
        // 查询就诊人信息
        List<Long> inquirers = videoConsults.stream().map(DrVideoConsult::getInquirerId).collect(Collectors.toList());
        List<PatientInquirerBO> patientInquirers = apFeignClient.getPatientInquirer(inquirers);
        Map<Long, PatientInquirerBO> inquirerInfoMap = patientInquirers.stream().collect(Collectors.toMap(PatientInquirerBO::getInquirerId, i -> i));
        // 查询视频咨询消息
        Set<Long> videoConsultIds = videoConsults.stream().map(DrVideoConsult::getId).collect(Collectors.toSet());
        List<VideoConsultMessage> videoConsultMessages = videoConsultMessageRepository.findByVideoConsultIdIn(videoConsultIds);
        Map<Long, Map<Integer, VideoConsultMessage>> videoConsultMessageMap = getVideoConsultMessageMap(videoConsultMessages);

        List<DrVideoGroupConsult> groupConsultList = groupConsultRepository.findByVideoConsultIdIn(videoConsultIds);
        Map<Long, DrVideoGroupConsult> groupConsultMap = groupConsultList.stream().collect(Collectors.toMap(DrVideoGroupConsult::getVideoConsultId, Function.identity(), (a, b) -> b));

        return PageVO.newBuilder()
                .pageNo(pageable.getPageNumber())
                .pageSize(pageable.getPageSize())
                .totalCount((int) videoConsultPage.getTotalElements())
                .result(videoConsults.stream().map(v -> {
                    Patient patient = patientMap.getOrDefault(v.getPatientId(), new Patient());
                    PatientInquirerBO inquirer = inquirerInfoMap.getOrDefault(v.getInquirerId(), new PatientInquirerBO());
                    if (inquirer.getChildTag() == 1) {
                        inquirer.setPhone(inquirer.getGuardianPhone());
                    inquirer.setIdCard(inquirer.getGuardianIdCard());
                }
                Map<Integer, VideoConsultMessage> messageMap = videoConsultMessageMap.get(v.getId());
                DoctorConsultListVO build = DoctorConsultListVO.builder().patientPhoto(patient.getHeadUrl())
                        .patientName(patient.getName()).inquirerAgeStr(inquirer.getAge())
                        .inquirerGender(inquirer.getGender()).inquirerName(inquirer.getName()).videoConsultId(v.getId())
                        .patientId(v.getPatientId()).status(v.getVideoConsultStatus()).endTime(v.getChangedAt().getTime())
                        .diseaseId(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_DISEASE))
                        .caseId(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_CASE))
                        .roomId(v.getRoomId()).inquirerId(v.getInquirerId())
                        .talkTime(Optional.ofNullable(v.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue()))
                                .orElse("--:--:--"))
                        .price(v.getPrice())
                        .remainderConfirmTime(
                                Optional.ofNullable(getRemainderConfirmTime(v.getConsultTime(), v.getVideoConsultStatus()))
                                        .map(t -> DateUtil.secondToTime(t.intValue())).orElse(null))
                        .remainderConfirmTimeSecond(getRemainderConfirmTime(v.getConsultTime(), v.getVideoConsultStatus()))
                        .pictureMessageFlg(Optional
                                .ofNullable(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_IMAGE))
                                .map(m -> 1).orElse(2))
                        .sendCaseFlg(Optional
                                .ofNullable(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_DRCASE))
                                .map(m -> 1).orElse(2))
                        .sendRecomFlg(Optional
                                .ofNullable(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_RECOM))
                                .map(m -> 1).orElse(2))
                        .inquirerInfo(inquirer).conditionDesc(v.getConditionDesc())
                        .diagnosis(Optional.ofNullable(v.getDiagnosis()).map(d -> Arrays.asList(d.split(","))).orElse(null))
                        .offlineDiagnosis(v.getOfflineDiagnosis())
                        .offlineMedicalRecordUrl(this.getOfflineMedicalRecordUrl(inquirer.getIdCard(), inquirer.getName()))
                        .onlineMedicalRecordUrl(this.getOnlineMedicalRecordUrl(inquirer.getIdCard(), inquirer.getName()))
                        .refusalReason(v.getRefusalReason()).createdAt(v.getCreatedAt()).changedAt(v.getChangedAt())
                        .groupConsultStatus(Optional.ofNullable(groupConsultMap.get(v.getId())).map(DrVideoGroupConsult::getGroupConsultStatus)
                                .orElse(DrVideoGroupConsult.GROUP_CONSULT_STATUS_EMPTY))
                        .build();
                return build;
            }).collect(Collectors.toList()))
                .build();
    }

    private Map<Long, Map<Integer, VideoConsultMessage>> getVideoConsultMessageMap(List<VideoConsultMessage> videoConsultMessageList) {
        Map<Long, List<VideoConsultMessage>> videoConsultMessageListMap = videoConsultMessageList.stream()
                .collect(Collectors.groupingBy(VideoConsultMessage::getVideoConsultId));
        Map<Long, Map<Integer, VideoConsultMessage>> resMap = new HashMap<>();
        for (Long key : videoConsultMessageListMap.keySet()) {
            List<VideoConsultMessage> videoConsultMessages = videoConsultMessageListMap.get(key);
            Map<Integer, VideoConsultMessage> map = videoConsultMessages.stream()
                    .collect(Collectors.toMap(VideoConsultMessage::getType, v -> v, (v1, v2) -> v1));
            resMap.put(key, map);
        }
        return resMap;
    }

    public String getContent(Map<Integer, VideoConsultMessage> map, Integer type) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        return Optional.ofNullable(map.get(type)).map(v -> v.getContent()).orElse(null);
    }

    @SneakyThrows
    private String getOfflineMedicalRecordUrl(String idCard, String name) {
        String url = ConfigUtil.getString("his.recordPageUrl");
        String key = DESUtil.desEncrypt(ConfigUtil.getString("his.hisSecret"), idCard + "|" + name);
        return String.format(url, URLEncoder.encode(key, "UTF-8"));
    }

    @SneakyThrows
    private String getOnlineMedicalRecordUrl(String idCard, String name) {
        String url = ConfigUtil.getString("his.onlineCaseUrl");
        String secretKey = ConfigUtil.getString("his.hisSecret");
        log.info("url: {}, secretKey: {}", url, secretKey);
        String key = DESUtil.desEncrypt(secretKey, idCard + "|" + name);
        return String.format(url, URLEncoder.encode(key, "UTF-8"));
    }

    /**
     * 获取剩余确认时间
     *
     * @param consultTime
     * @return
     */
    private static Long getRemainderConfirmTime(Date consultTime, Integer videoConsultStatus) {
        if (consultTime == null || videoConsultStatus != DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT) {
            return null;
        }
        long between = DateUtil.between(DateUtil.date(), consultTime, DateUnit.SECOND);
        long m = CommonConstant.VIDEO_CONSULT_AUTO_CANCEL_TIME * 60;
        if (m - between <= 0) {
            return null;
        }
        return m - between;
    }

    @Override
    public Integer toTreatNum(Long doctorId) {
        List<DrVideoConsult> videoConsult = videoConsultRepository.findByDoctorIdAndVideoConsultStatus(doctorId, DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT);
        return videoConsult.size();
    }

    @Override
    public List<RefusalReasonVO> refusalReasonList() {
        List<DrSystemConf> drSystemConfs = systemConfRepository.findByConfCode(DrSystemConfEnum.DR_VIDEO_CONSULT_REFUSAL_REASON.getCode());
        return drSystemConfs.stream().map(drSystemConf ->
                RefusalReasonVO.builder()
                        .id(drSystemConf.getId())
                        .content(drSystemConf.getConfValue())
                        .build())
                .collect(Collectors.toList());
    }

    private VideoConsultBO getVideoConsultBO(DrVideoConsult videoConsult) {
        if (videoConsult == null) {
            return null;
        }
        VideoConsultBO videoConsultBO = new VideoConsultBO();
        BeanUtil.copyProperties(videoConsult, videoConsultBO);
        videoConsultBO.setFinish(DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE == videoConsult.getVideoConsultStatus());
        return videoConsultBO;
    }

    @Override
    public List<String> getImageList(Long videoConsultId) {
        List<VideoConsultMessage> videoConsultMessages = this.videoConsultMessageRepository.findByVideoConsultId(videoConsultId);
        if (CollUtil.isEmpty(videoConsultMessages)) {
            return Collections.emptyList();
        }
        return videoConsultMessages.stream().filter(v -> v.getType() == 2).map(v -> {
            JSONObject jsonObject = JSON.parseObject(v.getContent());
            return imgHost + jsonObject.getString("path");
        }).collect(Collectors.toList());
    }

    @Override
    public DoctorConsultDetailVO doctorConsultDetail(Long videoConsultId) throws BusinessException {
        DrVideoConsult videoConsult = videoConsultRepository.get(videoConsultId);
        if (videoConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        Integer price = videoConsult.getPrice();
        Integer videoConsultStatus = videoConsult.getVideoConsultStatus();
        Long talkTime = videoConsult.getTalkTime();
        List<VideoConsultMessage> videoConsultMessages = videoConsultMessageRepository.findByVideoConsultId(videoConsultId);
        Map<Integer, VideoConsultMessage> map = videoConsultMessages.stream().collect(Collectors.toMap(VideoConsultMessage::getType, v -> v, (v1, v2) -> v1));
        Integer pictureMessageFlg = Optional.ofNullable(
                getContent(map, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_IMAGE)).map(v -> 1).orElse(2);
        Integer sendCaseFlg = Optional.ofNullable(
                getContent(map, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_DRCASE)).map(v -> 1).orElse(2);
        Integer sendRecomFlg = Optional
            .ofNullable(this.getContent(map, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_RECOM)).map(v -> 1).orElse(2);
        PatientInquirerBO inquirer =
            apFeignClient.getPatientInquirer(Arrays.asList(videoConsult.getInquirerId())).get(0);
        String idCard = inquirer.getIdCard();
        if (StringUtils.isBlank(idCard)) {
            inquirer.setIdCard("无");
        }
        String openOfflineMedical = ConfigUtil.getString("his.openOfflineMedical");
        return DoctorConsultDetailVO.builder()
                .videoConsultId(videoConsultId)
                .price(price)
                .status(videoConsultStatus)
                .talkTime(Optional.ofNullable(talkTime).map(t -> DateUtil.secondToTime(t.intValue())).orElse("--:--:--"))
                .pictureMessageFlg(pictureMessageFlg)
                .sendCaseFlg(sendCaseFlg)
                .sendRecomFlg(sendRecomFlg).conditionDesc(videoConsult.getConditionDesc()).inquirerInfo(inquirer)
                .diagnosis(
                        Optional.ofNullable(videoConsult.getDiagnosis()).map(d -> Arrays.asList(d.split(","))).orElse(null))
                .offlineDiagnosis(videoConsult.getOfflineDiagnosis())
                .offlineMedicalRecordUrl(this.getOfflineMedicalRecordUrl(idCard, inquirer.getName()))
                .onlineMedicalRecordUrl(
                        StringUtils.isBlank(idCard) ? null : this.getOnlineMedicalRecordUrl(idCard, inquirer.getName()))
            .openOfflineMedical("true".equals(openOfflineMedical) ? true : false)
                .build();
    }

    @Override
    public void sendDiscontinueMsg(Integer roomId) {
        DrVideoConsult videoConsult = videoConsultRepository.findByRoomId(roomId);
        Long patientId = videoConsult.getPatientId();
//        String msg = "通话中断 <a href='http://www.qq.com' data-miniprogram-appid='"
//                + wxMiniappAppid
//                + "' data-miniprogram-path='/pages/meeting/meeting?roomID="
//                + videoConsult.getRoomId()
//                + "&videoConsultId=" + videoConsult.getId()
//                + "'>【重新进入】</a>";
        this.sendVideoConsultMaMsg(videoConsult.getId(), videoConsult.getDoctorId(), patientId,
            "/pages/meeting/meeting?roomID=" + roomId + "&videoConsultId=" + videoConsult.getId(), "通话中断, 点击重新进入",
            videoConsult.getConditionDesc());
//        sendSystemMsg(patientId, msg);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateVideoConsultStatus(Integer roomId, Long talkTime, Integer videoConsultStatus, String userId) {
        DrVideoConsult videoConsult = this.videoConsultRepository.findByRoomId(roomId);
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE != videoConsult.getVideoConsultStatus()) {
            // 判断是否是专家进入/退出诊室
            if (userId != null && !videoConsult.getDoctorId().equals(Long.parseLong(userId))) {
                // 专家退出诊室删除专家id
                if (videoConsultStatus != null
                    && videoConsultStatus == DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE) {
                    RedisUtil.setOps().srem(VideoConsultConstant.VIDEO_CONSULT_SPECIALIST_CACHE_KEY + roomId, userId);
                } else {
                    RedisUtil.setOps().sadd(VideoConsultConstant.VIDEO_CONSULT_SPECIALIST_CACHE_KEY + roomId, userId);
                }
                return;
            }
            // 如果状态为空说明是进入诊室改状态为进行中
            if (videoConsultStatus == null) {
                videoConsult.setVideoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING);
            } else {
                videoConsult.setVideoConsultStatus(videoConsultStatus);
            }
            if (null != talkTime) {
                videoConsult.setTalkTime(talkTime);
            }
            videoConsult.setChangedAt(new Date());
            this.videoConsultRepository.update(videoConsult);
        }
    }

    @Override
    public PageVO<PatientConsultRecordVO> patientConsultRecordTest(Long patientId, PageParamVO pageParam) {
        ArrayList<Integer> status = ListUtil.toList(DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING, DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE);
        Page<DrVideoConsult> videoConsultPage = videoConsultRepository.findByPatientIdAndVideoConsultStatusInOrderByChangedAtDesc(patientId, status, pageParam.getPageable());
        if (CollectionUtils.isEmpty(videoConsultPage.getContent())) {
            return new PageVO<>();
        }
        List<DrVideoConsult> drVideoConsults = videoConsultPage.getContent();
        Set<Long> doctorIds = drVideoConsults.stream().map(DrVideoConsult::getDoctorId).collect(Collectors.toSet());
        List<Doctor> doctors = doctorRepository.findByIdIn(doctorIds);
        Map<Long, DoctorVO> doctorInfo = getDoctorInfo(doctors);
        return PageVO.newBuilder()
                .pageNo(pageParam.getPage())
                .pageSize(pageParam.getNum())
                .totalCount((int) videoConsultPage.getTotalElements())
                .result(drVideoConsults.stream().map(videoConsult ->
                        PatientConsultRecordVO.builder()
                                .consultTime(videoConsult.getChangedAt())
                                .department(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getDepartmentName()).orElse(""))
                                .hospital(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getHospitalName()).orElse(""))
                                .name(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getName()).orElse(""))
                                .title(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getTitle()).orElse(""))
                                .photo(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getPhoto()).orElse(""))
                                .talkTime(Optional.ofNullable(videoConsult.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue())).orElse("--:--:--"))
                                .roomId(videoConsult.getRoomId())
                                .videoConsultId(videoConsult.getId())
                                .build()
                ).collect(Collectors.toList()))
                .build();
    }

    @Override
    public List<VideoConsultMessageListVO> inRoom(Long videoConsultId) throws BusinessException {
        return inRoomVideoConsultMessages(videoConsultId, null);
    }

    @Override
    public void uploadThumbMedia(MultipartFile file) {
        String path = String.format(videoConsultPath, "thumb-media");
        String filePath = null;
        try {
            filePath = UploadUtil.uploadFile(basePath, path, file);
        } catch (IOException e) {
            log.error("上传永久素材时出现异常，exception:{}", e.getMessage());
        }
        String thumbMedia = apFeignClient.uploadThumbMedia(basePath + filePath);
        if (!StringUtils.isEmpty(thumbMedia)) {
            RedisUtil.valueOps().set(VideoConsultConstant.VIDEO_CONSULT_THUMB_MEDIA_CACHE_KEY, thumbMedia);
        }
    }

    public String getVideoSessionKey(Long doctorId, Long patientId) {
        return "video_consult_" + doctorId + "_" + patientId;
    }

    @Override
    public VideoConsultRoomConfigVO videoConsultRoomConfig(Integer roomId, String userId) throws BusinessException {
        DrVideoConsult drVideoConsult = videoConsultRepository.findByRoomId(roomId);
        if (null == drVideoConsult) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        if (DrVideoConsult.VIDEO_CONSULT_PAY_SUCCESS != drVideoConsult.getPayStatus()) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_PAY, "视频问诊未支付");
        }
        if (drVideoConsult.getDoctorId().equals(VideoConsultUtil.getId(userId))) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_IN_ROOM_FAIL, "该医生不可通过房间号进入");
        }
        Integer videoConsultStatus = drVideoConsult.getVideoConsultStatus();
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT == videoConsultStatus) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_YET_SEEN, "视频问诊还未被接诊");
        }
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL == videoConsultStatus
                || DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE == videoConsultStatus) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_IS_OVER, "视频问诊已结束");
        }
        PatientInquirerInfo patientInquirerInfo = patientInquirerInfoRepository.get(drVideoConsult.getInquirerId());
        if (patientInquirerInfo == null) {
            log.error("就诊人信息不存在 patientInquirerId:{}", drVideoConsult.getInquirerId());
            throw new BusinessException(BaseExceptionCodes.USER_NOT_EXIST, "就诊人信息不存在");
        }
        List<VideoConsultMessage> videoConsultMessages = videoConsultMessageRepository.findByVideoConsultId(drVideoConsult.getId());
        Map<Integer, VideoConsultMessage> map = videoConsultMessages.stream().collect(Collectors.toMap(VideoConsultMessage::getType, v -> v, (v1, v2) -> v1));
        String diseaseId = getContent(map, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_DISEASE);
        String caseId = getContent(map, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_CASE);
        return VideoConsultRoomConfigVO.builder()
                .appId(appId)
                .key(key)
                .userId(userId)
                .userSig(ImUtil.genUserSig(userId))
                .inquirerAge(patientInquirerInfo.getAge())
                .inquirerGender(patientInquirerInfo.getGender())
                .inquirerName(patientInquirerInfo.getName())
                .diseaseId(diseaseId)
                .caseId(caseId)
                .record(resultVideoConsultMessages(videoConsultMessages))
                .offlineRecordUrl(this.getOfflineMedicalRecordUrl(patientInquirerInfo.getIdCard(), patientInquirerInfo.getName()))
                .onlineRecordUrl(StrUtil.isBlank(patientInquirerInfo.getIdCard()) ? ""
                        : this.getOnlineMedicalRecordUrl(patientInquirerInfo.getIdCard(), patientInquirerInfo.getName()))
                .build();
    }

    @Override
    public UserInfoVO getUserInfo(String userId, Integer roomId) throws BusinessException {
        if (!VideoConsultUtil.containsDoctorPc(userId) && !VideoConsultUtil.containsDoctor(userId)) {
            DrVideoConsult videoConsult = videoConsultRepository.findByRoomId(roomId);
            if (videoConsult != null && videoConsult.getInquirerId() != null) {
                PatientInquirerInfo patientInquirerInfo = this.patientInquirerInfoRepository.get(videoConsult.getInquirerId());
                if (patientInquirerInfo == null) {
                    log.error("就诊人信息不存在 patientId:{}", userId);
                    throw new BusinessException(BaseExceptionCodes.USER_NOT_EXIST, "就诊人信息不存在");
                }
                return UserInfoVO.builder()
                        .type(1)
                        .name(patientInquirerInfo.getName())
                        .build();
            }
        }
        Long doctorId = null;
        if (VideoConsultUtil.containsDoctorPc(userId)) {
            doctorId = Long.valueOf(userId.replace("doctor-pc-", ""));
        } else if (VideoConsultUtil.containsDoctor(userId)) {
            doctorId = Long.valueOf(userId.replace("doctor-", ""));
        }
        Doctor doctor = doctorRepository.get(doctorId);
        if (doctor == null) {
            log.error("医生信息不存在 doctorId:{}", doctorId);
            throw new BusinessException(BaseExceptionCodes.USER_NOT_EXIST, "医生信息不存在");
        }
        return UserInfoVO.builder()
                .type(2)
                .name(doctor.getName())
                .build();
    }

    @Override
    public PageVO<VideoConsultRecordBO> consultVideoList(Long patientId, int page, int num) throws BusinessException {
        ArrayList<Integer> status = ListUtil.toList(DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT,DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING, DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE,DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL,DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE);
        Page<DrVideoConsult> videoConsultPage = videoConsultRepository.findByPatientIdAndVideoConsultStatusInOrderByChangedAtDesc(patientId,status, PageRequest.of(page == 0 ? 0 : page - 1, num));
        if (CollectionUtils.isEmpty(videoConsultPage.getContent())) {
            return new PageVO<>();
        }
        List<DrVideoConsult> drVideoConsults = videoConsultPage.getContent();
        Set<Long> doctorIds = drVideoConsults.stream().map(DrVideoConsult::getDoctorId).collect(Collectors.toSet());
        List<Doctor> doctors = doctorRepository.findByIdIn(doctorIds);
        Map<Long, DoctorVO> doctorInfo = getDoctorInfo(doctors);
        return PageVO.newBuilder()
                .pageNo(page)
                .pageSize(num)
                .totalCount((int) videoConsultPage.getTotalElements())
                .result(drVideoConsults.stream().map(videoConsult ->
                        VideoConsultRecordBO.builder()
                                .consultTime(videoConsult.getChangedAt())
                // .refusalReason(videoConsult.getRefusalReasonId() != null ?
                // getRefusalReason(videoConsult.getRefusalReasonId()) : "")
                .refusalReason(videoConsult.getRefusalReason())
                                .videoConsultStatus(videoConsult.getVideoConsultStatus())
                                .department(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getDepartmentName()).orElse(""))
                                .hospital(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getHospitalName()).orElse(""))
                                .name(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getName()).orElse(""))
                                .title(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getTitle()).orElse(""))
                                .photo(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getPhoto()).orElse(""))
                                .talkTime(Optional.ofNullable(videoConsult.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue())).orElse("--:--:--"))
                                .roomId(videoConsult.getRoomId())
                                .videoConsultId(videoConsult.getId())
                                .build()
                ).collect(Collectors.toList()))
                .build();
    }

    /**
     * 获取医生视频问诊数量（status不传默认全部）
     *
     * @param doctorId
     * @param videoConsultStatus
     * @return
     * @throws BusinessException
     */
    @Override
    public Integer getCountByStatus(Long doctorId, Integer videoConsultStatus) throws BusinessException {
        Integer resultCount = 0;
        if (null != videoConsultStatus && DrVideoConsult.VIDEO_CONSULT_STATUS_ALL != videoConsultStatus) {
            List<DrVideoConsult> videoConsult = videoConsultRepository.findByDoctorIdAndVideoConsultStatus(doctorId, videoConsultStatus);
            resultCount = videoConsult.size();
        } else {
            resultCount = videoConsultRepository.findCountByDoctorId(doctorId);
        }

        return resultCount;
    }

    /**
     * 根据医生id获取视频问诊订单信息
     * @param doctorId
     * @return
     * @throws BusinessException
     */
    @Override
    public List<VideoConsultBO> getOrderByDoctorId(Long doctorId) throws BusinessException {
        List<DrVideoConsult> videoConsultIterable = videoConsultRepository.findByDoctorId(doctorId);
        List<VideoConsultBO> result = new ArrayList<>();
        videoConsultIterable.forEach(drVideoConsult -> result.add(getVideoConsultBO(drVideoConsult)));
        return result;
    }

    /**
     * 发送小程序视频问诊消息publishMsg2VideoConsult
     *
     * @param videoConsultId
     * @param doctorId
     * @param patientId
     * @param path
     * @param content
     * @param conditionDesc
     *            病情描述
     */
    private void sendVideoConsultMaMsg(Long videoConsultId, Long doctorId, Long patientId, String path, String content,
        String conditionDesc) {
        // VideoConsultMessage videoConsultMessage =
        // this.videoConsultMessageRepository.findFirstByVideoConsultIdAndType(videoConsultId, 3);
        // if (null == videoConsultMessage) {
        // log.error("病情为空：videoConsultId: {}, doctorId: {}, patientId: {}", videoConsultId, doctorId, patientId);
        // return;
        // }
        MsgMaPublishParamBO publishParamBO = MsgMaPublishParamBO.builder()
                .doctorId(doctorId)
                .patientId(patientId)
                .path(path)
                .msgType(MsgMaPublishParamBO.MsgType.VIDEO_CONSULT)
                .content(content)
            .diseaseId(conditionDesc)
                .build();
        log.info("发送小程序订阅消息 param: {}", JSONUtil.toJsonStr(publishParamBO));
        // 发送小程序消息
        this.apFeignClient.publishMaMessage(publishParamBO);
    }

    @Override
    public String getCasePdf(Long videoConsultId) throws BusinessException {
        VideoConsultMessage videoConsultMessage = videoConsultMessageRepository
            .findFirstByVideoConsultIdAndType(videoConsultId, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_DRCASE);
        if (videoConsultMessage == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_SEND_CASE, "当前视频问诊未推送过病例信息");
        }
        return emrFeignClient.caseSignPdfUrl(videoConsultMessage.getContent());
    }

    @Override
    public List<RecomPrescriptionBO> recomList(Long videoConsultId) throws BusinessException {
        List<VideoConsultMessage> videoConsultMessages = videoConsultMessageRepository
            .findByVideoConsultIdAndType(videoConsultId, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_RECOM);
        if (CollectionUtil.isEmpty(videoConsultMessages)) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_SEND_RECOM, "当前视频问诊未推送过处方信息");
        }
        List<Long> recomIdList =
            videoConsultMessages.stream().map(vc -> Long.valueOf(vc.getContent())).collect(Collectors.toList());
        List<RecomPrescriptionBO> recomPrescriptionList = recommendFeignClient.recomPrescriptionList(recomIdList);
        recomPrescriptionList.forEach(res -> res.setPdfUrl(imgHost + res.getPdfUrl()));
        return recomPrescriptionList;
    }

    @Override
    public PageVO<PatientConsultRecordVO> getFinishedVideoConsultsPage(PageParamVO pageParam, List<Long> inquirerIds) {
        Page<DrVideoConsult> videoConsultPage =
            videoConsultRepository.findByInquirerIdInAndVideoConsultStatus(inquirerIds,
                DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE, pageParam.getPageable(Sort.by("changedAt").descending()));
        return this.transferPatientConsultRecordVO(videoConsultPage);
    }

    @Override
    public PatientConsultRecordVO getPatientConsultRecord(Long videoConsultId) {
        DrVideoConsult videoConsult = this.videoConsultRepository.get(videoConsultId);
        List<Doctor> doctors = doctorRepository.findByIdIn(Lists.newArrayList(videoConsult.getDoctorId()));
        Map<Long, DoctorVO> doctorInfo = this.getDoctorInfo(doctors);
        return PatientConsultRecordVO.builder().videoConsultId(videoConsult.getId())
            .consultTime(videoConsult.getCreatedAt())
            .department(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getDepartmentName())
                .orElse(""))
            .hospital(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getHospitalName())
                .orElse(""))
            .name(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getName()).orElse(""))
            .title(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getTitle()).orElse(""))
            .photo(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getPhoto()).orElse(""))
            .talkTime(Optional.ofNullable(videoConsult.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue()))
                .orElse("--:--:--"))
            .roomId(videoConsult.getRoomId()).diagnosis(videoConsult.getDiagnosis()).build();
    }

    @Override
    public void leave(Integer roomId, Long doctorId) {
        boolean existsKey = RedisUtil.keyOps().existsKey(VideoConsultConstant.VIDEO_CONSULT_CACHE_KEY + roomId);
        if (existsKey) {
            String cacheStr = RedisUtil.valueOps().getString(VideoConsultConstant.VIDEO_CONSULT_CACHE_KEY + roomId);
            VideoConsultCacheVO cacheVO = JSON.parseObject(cacheStr, VideoConsultCacheVO.class);
            Integer status = cacheVO.getStatus();
            if (VideoConsultCacheVO.DOCTOR_APP_AND_PATIENT_EXIST == status
                || VideoConsultCacheVO.DOCTOR_PC_AND_PATIENT_EXIST == status) {
                cacheVO.setStatus(VideoConsultCacheVO.PATIENT_EXIST);
            } else if (VideoConsultCacheVO.DOCTOR_APP_EXIST == status
                || VideoConsultCacheVO.DOCTOR_PC_EXIST == status) {
                cacheVO.setStatus(VideoConsultCacheVO.NOT_EXIST);
            }
            RedisUtil.valueOps().set(VideoConsultConstant.VIDEO_CONSULT_CACHE_KEY + roomId, JSON.toJSONString(cacheVO));
        }
        this.updateVideoConsultStatus(roomId, null, DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE,
            String.valueOf(doctorId));
    }

    private PageVO<PatientConsultRecordVO> transferPatientConsultRecordVO(Page<DrVideoConsult> videoConsultPage) {
        if (CollectionUtils.isEmpty(videoConsultPage.getContent())) {
            return new PageVO<>();
        }
        List<DrVideoConsult> drVideoConsults = videoConsultPage.getContent();
        Set<Long> doctorIds = drVideoConsults.stream().map(DrVideoConsult::getDoctorId).collect(Collectors.toSet());
        List<Doctor> doctors = doctorRepository.findByIdIn(doctorIds);
        Map<Long, DoctorVO> doctorInfo = this.getDoctorInfo(doctors);
        return PageVO.newBuilder().pageNo(videoConsultPage.getNumber()).pageSize(videoConsultPage.getSize())
            .totalCount((int)videoConsultPage.getTotalElements())
            .result(drVideoConsults.stream().map(videoConsult -> PatientConsultRecordVO.builder()
                .videoConsultId(videoConsult.getId()).consultTime(videoConsult.getChangedAt())
                .department(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId()))
                    .map(d -> d.getDepartmentName()).orElse(""))
                .inquirerId(videoConsult.getInquirerId())
                .hospital(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getHospitalName())
                    .orElse(""))
                .name(Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getName()).orElse(""))
                .title(
                    Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getTitle()).orElse(""))
                .photo(
                    Optional.ofNullable(doctorInfo.get(videoConsult.getDoctorId())).map(d -> d.getPhoto()).orElse(""))
                .talkTime(Optional.ofNullable(videoConsult.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue()))
                    .orElse("--:--:--"))
                .roomId(videoConsult.getRoomId()).diagnosis(videoConsult.getDiagnosis()).build())
                .collect(Collectors.toList()))
            .build();
    }

    /**
     * 进入房间返回聊天记录
     *
     * @param videoConsultId
     * @param accept
     *            不为空则为接诊
     *
     * @return
     */
    private List<VideoConsultMessageListVO> inRoomVideoConsultMessages(Long videoConsultId, Boolean accept)
        throws BusinessException {
        DrVideoConsult drVideoConsult = this.videoConsultRepository.get(videoConsultId);
        if (null == drVideoConsult) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        if (DrVideoConsult.VIDEO_CONSULT_PAY_SUCCESS != drVideoConsult.getPayStatus()) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_PAY, "视频问诊未支付");
        }
        Integer videoConsultStatus = drVideoConsult.getVideoConsultStatus();
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL == videoConsultStatus
            || DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE == videoConsultStatus) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_IS_OVER, "视频问诊已结束");
        }
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE == videoConsultStatus
            || DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING == videoConsultStatus) {
            // 中断，并返回之前的聊天记录
            List<VideoConsultMessage> videoConsultMessages =
                this.videoConsultMessageRepository.findByVideoConsultId(videoConsultId);
            return resultVideoConsultMessages(videoConsultMessages);
        }
        drVideoConsult.setIsTreat(0);
        drVideoConsult.setVideoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_PROCESSING);
        this.videoConsultRepository.update(drVideoConsult);
        // 为医生接诊
        if (accept != null && accept) {
            Long patientId = drVideoConsult.getPatientId();
            Long doctorId = drVideoConsult.getDoctorId();
            // String mediaId =
            // RedisUtil.valueOps().getString(VideoConsultConstant.VIDEO_CONSULT_THUMB_MEDIA_CACHE_KEY);
            // if (StringUtils.isBlank(mediaId)) {
            // mediaId = thumbMediaId;
            // }
            this.sendVideoConsultMaMsg(videoConsultId, doctorId, patientId, "/pages/meeting/meeting?roomID="
                + drVideoConsult.getRoomId() + "&videoConsultId=" + drVideoConsult.getId(), "您的视频申请医生已接诊，请快速加入诊室",
                drVideoConsult.getConditionDesc());
            // sendMiniProgramMsg(patientId, "医生已接诊，点击进入诊室。", wxMiniappAppid, "/pages/meeting/meeting?roomID="
            // + drVideoConsult.getRoomId() + "&videoConsultId=" + drVideoConsult.getId(), mediaId);
        }
        return Collections.emptyList();
    }

    /**
     * 处理返回聊天记录
     *
     * @param videoConsultMessages
     * @return
     */
    private List<VideoConsultMessageListVO> resultVideoConsultMessages(List<VideoConsultMessage> videoConsultMessages) {
        if (CollUtil.isEmpty(videoConsultMessages)) {
            return Collections.emptyList();
        }
        return videoConsultMessages.stream().filter(v -> v.getType() == 2).map(v -> {
            JSONObject jsonObject = JSON.parseObject(v.getContent());
            jsonObject.put("id", v.getId());
            jsonObject.put("path", imgHost + jsonObject.getString("path"));
            return VideoConsultMessageListVO.builder().id(v.getId()).type(v.getType())
                    .content(jsonObject.toJSONString()).build();
        }).collect(Collectors.toList());
    }


    @Override
    public GroupConsultApplyVO groupConsultInfo(Long videoConsultId, Long doctorId) throws BusinessException {

        DrVideoConsult videoConsult = videoConsultRepository.get(videoConsultId);
        if (videoConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        DrVideoGroupConsult groupConsult = groupConsultRepository.findFirstByVideoConsultId(videoConsultId);
        if (groupConsult == null) {
            groupConsult = new DrVideoGroupConsult();
        }
        PatientInquirerBO inquirer = apFeignClient.getPatientInquirer(Collections.singletonList(videoConsult.getInquirerId())).get(0);

        String hospitalIdStr = RedisUtil.valueOps().getString(CommonConstant.REDIS_PRE_DR + "group_consult_hospital");
        if (!NumberUtil.isNumber(hospitalIdStr)) {
            //TODO 默认北京友谊医院，硬编码
            hospitalIdStr = "41138";
        }
        Integer hospitalId = NumberUtil.parseInt(hospitalIdStr);
        Hospital hospital = hospitalRepository.get(hospitalId);
        List<Doctor> targetDoctors = doctorRepository.findByHospitalId(hospitalId);
        Doctor doctor = doctorRepository.get(videoConsult.getDoctorId());
        Department department = departmentRepository.get(doctor.getDepartmentId());

        return GroupConsultApplyVO.builder()
                .videoConsultId(videoConsult.getId())
                .roomId(videoConsult.getRoomId())
                .inquirerInfo(inquirer)
                .inquirerId(inquirer.getInquirerId())
                .doctorId(videoConsult.getDoctorId())
                .patientId(videoConsult.getPatientId())
                .inquirerName(inquirer.getName())
                .inquirerAgeStr(inquirer.getAge())
                .inquirerGender(inquirer.getGender())
                .groupConsultDate(groupConsult.getGroupConsultDate())
                .groupConsultTimeStart(groupConsult.getGroupConsultTimeStart())
                .groupConsultTimeEnd(groupConsult.getGroupConsultTimeEnd())
                .groupConsultStatus(ObjectUtil.defaultIfNull(groupConsult.getGroupConsultStatus(), DrVideoGroupConsult.GROUP_CONSULT_STATUS_EMPTY))
                .groupConsultReason(groupConsult.getGroupConsultReason())
                .targetDoctor(groupConsult.getTargetDoctorName())
                .targetDoctorId(groupConsult.getTargetDoctorId())
                .sourceDoctor(StrUtil.blankToDefault(groupConsult.getDoctorName(), doctor.getName()))
                .departmentName(StrUtil.blankToDefault(groupConsult.getDepartmentName(), department == null ? doctor.getCustomDepartment() : department.getName()))
                .targetHospitalName(StrUtil.blankToDefault(groupConsult.getTargetHospitalName(), hospital.getName()))
                .targetDoctorList(targetDoctors.stream().map(d -> GroupConsultApplyVO.DoctorInfo.builder().id(d.getId()).name(d.getName()).phone(d.getPhone()).build()).collect(Collectors.toList()))
                .build();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void groupConsultApply(GroupConsultApplyVO groupConsultApplyVO) throws BusinessException {
        DrVideoConsult videoConsult = videoConsultRepository.get(groupConsultApplyVO.getVideoConsultId());
        if (videoConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE != videoConsult.getVideoConsultStatus()) {
            throw new BusinessException(ExceptionCodes.VIDEO_CONSULT_NOT_LEGAL_STATUS, "当前视频问诊状态不允许该操作");
        }
        Doctor doctor = doctorRepository.get(groupConsultApplyVO.getDoctorId());
        Doctor targetDoctor = doctorRepository.get(groupConsultApplyVO.getTargetDoctorId());
        DrVideoGroupConsult groupConsult = groupConsultRepository.findFirstByVideoConsultId(groupConsultApplyVO.getVideoConsultId());
        if (groupConsult == null) {
            groupConsult = new DrVideoGroupConsult();
            cn.hutool.core.bean.BeanUtil.copyProperties(groupConsultApplyVO, groupConsult);
            groupConsult.setTargetDoctorName(targetDoctor.getName());
            groupConsult.setTargetDoctorPhone(targetDoctor.getPhone());
            groupConsult.setDoctorName(doctor.getName());
            String hospitalIdStr = RedisUtil.valueOps().getString(CommonConstant.REDIS_PRE_DR + "group_consult_hospital");
            if (!NumberUtil.isNumber(hospitalIdStr)) {
                //TODO 默认北京友谊医院，硬编码
                hospitalIdStr = "41138";
            }
            Integer hospitalId = NumberUtil.parseInt(hospitalIdStr);
            groupConsult.setGroupConsultStatus(DrVideoGroupConsult.GROUP_CONSULT_STATUS_WAITING);
            groupConsult.setTargetHospitalId(hospitalId);
            groupConsult.setCreatedAt(new Date());
            groupConsult.setCreatedBy(groupConsultApplyVO.getSourceDoctor());
        } else {
            if (DrVideoGroupConsult.GROUP_CONSULT_STATUS_WAITING != groupConsult.getGroupConsultStatus()) {
                throw new BusinessException(ExceptionCodes.VIDEO_CONSULT_NOT_LEGAL_STATUS, "当前会诊状态不允许改操作");
            }
            groupConsult.setGroupConsultDate(groupConsultApplyVO.getGroupConsultDate());
            groupConsult.setGroupConsultTimeEnd(groupConsultApplyVO.getGroupConsultTimeEnd());
            groupConsult.setGroupConsultTimeStart(groupConsultApplyVO.getGroupConsultTimeStart());
            groupConsult.setChangedAt(new Date());
            groupConsult.setChangedBy(groupConsultApplyVO.getSourceDoctor());
        }
        groupConsultRepository.saveOrUpdate(groupConsult);

        // String destroyGroup = ImUtil.destroyGroup(String.valueOf(videoConsult.getRoomId()));
        // log.info("destroy group result:{}", destroyGroup);
        DismissRoomResponse dismissRoom = TrtcUtil.dismissRoom(videoConsult.getRoomId().longValue());
        log.info("dismiss room result:{}", JSONUtil.toJsonStr(dismissRoom));

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public GroupConsultPreSignBO groupConsultOpinion(GroupConsultOpinionVO groupConsultOpinionVO) throws BusinessException {
        DrVideoConsult videoConsult = videoConsultRepository.get(groupConsultOpinionVO.getVideoConsultId());
        DrVideoGroupConsult groupConsult = groupConsultRepository.findFirstByVideoConsultId(groupConsultOpinionVO.getVideoConsultId());
        if (videoConsult == null || groupConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        if (DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE != videoConsult.getVideoConsultStatus()) {
            throw new BusinessException(ExceptionCodes.VIDEO_CONSULT_NOT_LEGAL_STATUS, "当前视频问诊状态不允许该操作");
        }
        if (DrVideoGroupConsult.GROUP_CONSULT_STATUS_SOURCE_OPINION != groupConsult.getGroupConsultStatus()
                && DrVideoGroupConsult.GROUP_CONSULT_STATUS_TARGET_OPINION != groupConsult.getGroupConsultStatus()) {
            throw new BusinessException(ExceptionCodes.VIDEO_CONSULT_NOT_LEGAL_STATUS, "当前视频问诊状态不允许该操作");
        }


        int opinionType = GroupConsultSignParamBO.OPTION_TYPE_TARGET;
        String targetOpinion = "";
        String sourceOpinion = "";
        Date targetOpinionAt = groupConsult.getTargetOpinionAt();
        Date sourceOpinionAt = groupConsult.getSourceOpinionAt();
        if (GroupConsultOpinionVO.OPINION_FROM_SOURCE == groupConsultOpinionVO.getFrom()) {

            opinionType = GroupConsultSignParamBO.OPTION_TYPE_SOURCE;
            sourceOpinion = groupConsultOpinionVO.getOpinion();
            sourceOpinionAt = new Date();

            RedisUtil.valueOps().set(GROUP_CONSULT_CACHE_KEY + groupConsult.getId() + "_diagnosis", groupConsultOpinionVO.getDiagnoseData());
        } else if (GroupConsultOpinionVO.OPINION_FROM_TARGET == groupConsultOpinionVO.getFrom()) {

            opinionType = GroupConsultSignParamBO.OPTION_TYPE_TARGET;
            targetOpinion = groupConsultOpinionVO.getOpinion();
            targetOpinionAt = new Date();
        }

        RedisUtil.hashOps().hset(GROUP_CONSULT_CACHE_KEY + groupConsult.getId(), opinionType + "", groupConsultOpinionVO.getOpinion());

        GroupConsultPreSignBO presign = eSignFeignClient.presign(GroupConsultPreSignParamBO.builder()
                .groupConsultReason(groupConsult.getGroupConsultReason())
                .groupConsultId(groupConsult.getId())
                .inquirerId(groupConsult.getInquirerId())
                .departmentName(groupConsult.getDepartmentName())
                .opinionType(opinionType)
                .patientId(groupConsult.getPatientId())
                .sourceDoctorId(groupConsult.getDoctorId())
                .sourceDoctorName(groupConsult.getDoctorName())
                .sourceOpinion(StringUtils.defaultIfBlank(groupConsult.getSourceOpinion(), sourceOpinion))
                .sourceOpinionAt(sourceOpinionAt)
                .targetOpinion(StringUtils.defaultIfBlank(groupConsult.getTargetOpinion(), targetOpinion))
                .targetOpinionAt(targetOpinionAt)
                .targetDoctorName(groupConsult.getTargetDoctorName())
                .targetDoctorId(groupConsult.getTargetDoctorId())
                .targetHospitalId(groupConsult.getTargetHospitalId())
                .targetHospitalName(groupConsult.getTargetHospitalName())
                .diagnosis(Optional.ofNullable(groupConsultOpinionVO.getDiagnoseData()).orElse(Collections.emptyList()))
                .build());

        RedisUtil.hashOps().hset(GROUP_CONSULT_CACHE_KEY + groupConsult.getId(), opinionType + "_unionId", presign.getUniqueId());

        groupConsult.setPdfPath(presign.getFilePath());
        groupConsultRepository.update(groupConsult);

        return presign;
    }

    @Override
    public void groupConsultOpinionConfirm(GroupConsultOpinionConfirmVO opinionConfirmVO) throws BusinessException {

        DrVideoConsult videoConsult = videoConsultRepository.get(opinionConfirmVO.getVideoConsultId());
        DrVideoGroupConsult groupConsult = groupConsultRepository.findFirstByVideoConsultId(opinionConfirmVO.getVideoConsultId());
        if (videoConsult == null || groupConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }

        int opinionType = GroupConsultSignParamBO.OPTION_TYPE_TARGET;

        if (GroupConsultOpinionVO.OPINION_FROM_SOURCE == opinionConfirmVO.getFrom()) {
            opinionType = GroupConsultSignParamBO.OPTION_TYPE_SOURCE;
        } else if (GroupConsultOpinionVO.OPINION_FROM_TARGET == opinionConfirmVO.getFrom()) {
            opinionType = GroupConsultSignParamBO.OPTION_TYPE_TARGET;
        }

        String unionId = RedisUtil.hashOps().hget(GROUP_CONSULT_CACHE_KEY + groupConsult.getId(), opinionType + "_unionId");

        eSignFeignClient.sign(
                GroupConsultSignParamBO.builder()
                        .groupConsultId(groupConsult.getId())
                        .inquirerId(groupConsult.getInquirerId())
                        .unionId(unionId)
                        .opinionType(opinionType)
                        .pdfPath(groupConsult.getPdfPath())
                        .patientId(groupConsult.getPatientId())
                        .sourceDoctorId(groupConsult.getDoctorId())
                        .sourceDoctorName(groupConsult.getDoctorName())
                        .targetDoctorName(groupConsult.getTargetDoctorName())
                        .targetDoctorId(groupConsult.getTargetDoctorId())
                        .build());

    }

    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void groupConsultOpinionCallback(GroupConsultSignCallbackBO groupConsultSignCallbackBO) throws BusinessException {
        DrVideoGroupConsult groupConsult = groupConsultRepository.get(groupConsultSignCallbackBO.getGroupConsultId());
        if (groupConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        DrVideoConsult videoConsult = videoConsultRepository.get(groupConsult.getVideoConsultId());
        if (videoConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }

        String opinion = RedisUtil.hashOps().hget(GROUP_CONSULT_CACHE_KEY + groupConsult.getId(), groupConsultSignCallbackBO.getOpinionType() + "");

        if (groupConsultSignCallbackBO.getOpinionType() == GroupConsultSignParamBO.OPTION_TYPE_TARGET) {
            groupConsult.setTargetOpinion(opinion);
            groupConsult.setTargetOpinionAt(new Date());
            groupConsult.setChangedBy(groupConsult.getTargetDoctorName());
            groupConsult.setGroupConsultStatus(DrVideoGroupConsult.GROUP_CONSULT_STATUS_SOURCE_OPINION);
            groupConsult.setPdfPath(groupConsultSignCallbackBO.getFilePath());

            groupConsult.setTargetPdf(groupConsultSignCallbackBO.getFilePath());
            groupConsult.setPdfPath(groupConsultSignCallbackBO.getFilePath());

            RedisUtil.hashOps().hset(GROUP_CONSULT_CACHE_KEY + groupConsult.getId(), "_sealImage", groupConsultSignCallbackBO.getSealImage());

        } else if (groupConsultSignCallbackBO.getOpinionType() == GroupConsultSignParamBO.OPTION_TYPE_SOURCE) {
            List<String> diagnosis = (List<String>) RedisUtil.valueOps().getObject(GROUP_CONSULT_CACHE_KEY + groupConsult.getId() + "_diagnosis");
            String sealImagePath = RedisUtil.hashOps().hget(GROUP_CONSULT_CACHE_KEY + groupConsult.getId(), "_sealImage");

            String targetPath = groupConsultSignCallbackBO.getFilePath();
            log.info("会诊资料专家签名处理：basePath {},sealImagePath {},diagnosis {}, cache {}", basePath, sealImagePath, diagnosis, JacksonUtils.toJson(RedisUtil.hashOps().hgetAll(GROUP_CONSULT_CACHE_KEY + groupConsult.getId())));
            if (StrUtil.isNotBlank(sealImagePath)) {
                targetPath = targetPath.replaceAll(".pdf", "_extra.pdf");
                File targetFile = new File(basePath + targetPath);
                FileUtils.forceMkdirParent(targetFile);
                PdfReader reader = new PdfReader(basePath + groupConsultSignCallbackBO.getFilePath());
                PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(targetFile));
                PdfContentByte under = stamper.getOverContent(1);
                File sealFile = new File(basePath + sealImagePath);
                if (sealFile.exists()) {
                    Image image = Image.getInstance(basePath + sealImagePath);
                    image.setAbsolutePosition(440, 445);
                    image.scaleToFit(100, 100);
                    under.addImage(image);
                }
                stamper.close();
            }

            groupConsult.setSourceOpinion(opinion);
            groupConsult.setSourceOpinionAt(new Date());
            groupConsult.setChangedBy(groupConsult.getDoctorName());
            groupConsult.setGroupConsultStatus(DrVideoGroupConsult.GROUP_CONSULT_STATUS_COMPLETE);

            groupConsult.setSourcePdf(groupConsultSignCallbackBO.getFilePath());
            groupConsult.setPdfPath(targetPath);

            // 问诊完成走远问诊相关逻辑
            this.finish(videoConsult.getRoomId(), Optional.ofNullable(diagnosis).map(list -> String.join(",", list)).orElse(null));

            RedisUtil.keyOps().delete(GROUP_CONSULT_CACHE_KEY + groupConsult.getId());
        }
        groupConsult.setChangedAt(new Date());
        groupConsultRepository.update(groupConsult);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void groupConsultFinish(Integer roomId) throws BusinessException {
        DrVideoGroupConsult groupConsult = groupConsultRepository.findFirstByRoomId(roomId);
        if (groupConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        if (DrVideoGroupConsult.GROUP_CONSULT_STATUS_WAITING != groupConsult.getGroupConsultStatus()
                && DrVideoGroupConsult.GROUP_CONSULT_STATUS_TARGET_OPINION != groupConsult.getGroupConsultStatus()) {
            throw new BusinessException(ExceptionCodes.VIDEO_CONSULT_NOT_LEGAL_STATUS, "当前视频问诊状态不允许该操作");
        }
        DrVideoConsult videoConsult = videoConsultRepository.get(groupConsult.getVideoConsultId());
        if (videoConsult == null) {
            throw new BusinessException(BaseExceptionCodes.VIDEO_CONSULT_NOT_EXIST, "视频问诊不存在");
        }
        groupConsult.setGroupConsultStatus(DrVideoGroupConsult.GROUP_CONSULT_STATUS_TARGET_OPINION);
        groupConsult.setChangedAt(new Date());
        groupConsult.setChangedBy("SYSTEM");
        groupConsultRepository.saveOrUpdate(groupConsult);

        videoConsult.setPayStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_DISCONTINUE);
        videoConsult.setChangedAt(new Date());
        videoConsult.setChangedBy("SYSTEM");
        videoConsultRepository.saveOrUpdate(videoConsult);

        String destroyGroup = ImUtil.destroyGroup(String.valueOf(roomId));
        log.info("destroy group result:{}", destroyGroup);
        DismissRoomResponse dismissRoom = TrtcUtil.dismissRoom(roomId.longValue());
        log.info("dismiss room result:{}", JSONUtil.toJsonStr(dismissRoom));
    }

    @Override
    public PageVO<GroupConsultListVO> groupConsultList(Long doctorId, PageParamVO pageParam) throws BusinessException {
        Page<DrVideoGroupConsult> consultPage = groupConsultRepository.findByDoctorIdOrTargetDoctorIdOrderByIdDesc(doctorId, doctorId, pageParam.getPageable());
        List<Long> videoConsultIdList = consultPage.getContent().stream().map(DrVideoGroupConsult::getVideoConsultId).collect(Collectors.toList());
        List<DrVideoConsult> videoConsults = videoConsultRepository.findByIdIn(videoConsultIdList);
        Map<Long, DrVideoConsult> videoConsultMap = videoConsults.stream().collect(Collectors.toMap(DrVideoConsult::getId, Function.identity(), (a, b) -> b));

        // 查询患者信息
        Set<Long> patientIds = videoConsults.stream().map(DrVideoConsult::getPatientId).collect(Collectors.toSet());
        List<Patient> patients = patientRepository.findByIdIn(patientIds);
        Map<Long, Patient> patientMap = patients.stream().collect(Collectors.toMap(Patient::getId, p -> p));
        // 查询就诊人信息
        List<Long> inquirers = videoConsults.stream().map(DrVideoConsult::getInquirerId).collect(Collectors.toList());
        List<PatientInquirerBO> patientInquirers = apFeignClient.getPatientInquirer(inquirers);
        Map<Long, PatientInquirerBO> inquirerInfoMap = patientInquirers.stream().collect(Collectors.toMap(PatientInquirerBO::getInquirerId, i -> i));
        // 查询视频咨询消息
        Set<Long> videoConsultIds = videoConsults.stream().map(DrVideoConsult::getId).collect(Collectors.toSet());
        List<VideoConsultMessage> videoConsultMessages = videoConsultMessageRepository.findByVideoConsultIdIn(videoConsultIds);
        Map<Long, Map<Integer, VideoConsultMessage>> videoConsultMessageMap = this.getVideoConsultMessageMap(videoConsultMessages);

        return PageVO.newBuilder()
                .pageNo(pageParam.getPageable().getPageNumber())
                .pageSize(pageParam.getPageable().getPageSize())
                .totalCount((int) consultPage.getTotalElements())
                .result(consultPage.getContent().stream().map(videoGroupConsult -> {
                    DrVideoConsult videoConsult = videoConsultMap.get(videoGroupConsult.getVideoConsultId());
                    Patient patient = patientMap.getOrDefault(videoConsult.getPatientId(), new Patient());
                    PatientInquirerBO inquirer = inquirerInfoMap.getOrDefault(videoConsult.getInquirerId(), new PatientInquirerBO());
                    Map<Integer, VideoConsultMessage> messageMap = videoConsultMessageMap.get(videoConsult.getId());
                    DoctorConsultListVO consultListVO = DoctorConsultListVO.builder()
                            .patientPhoto(patient.getHeadUrl())
                            .patientName(patient.getName())
                            .inquirerAgeStr(inquirer.getAge())
                            .inquirerGender(inquirer.getGender())
                            .inquirerName(inquirer.getName()).videoConsultId(videoConsult.getId())
                            .patientId(videoConsult.getPatientId())
                            .status(videoConsult.getVideoConsultStatus())
                            .endTime(videoConsult.getChangedAt().getTime())
                            .diseaseId(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_DISEASE))
                            .caseId(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_CASE))
                            .roomId(videoConsult.getRoomId()).inquirerId(videoConsult.getInquirerId())
                            .talkTime(Optional.ofNullable(videoConsult.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue())).orElse("--:--:--"))
                            .price(videoConsult.getPrice())
                            .remainderConfirmTime(Optional.ofNullable(getRemainderConfirmTime(videoConsult.getConsultTime(), videoConsult.getVideoConsultStatus()))
                                    .map(t -> DateUtil.secondToTime(t.intValue())).orElse(null))
                            .remainderConfirmTimeSecond(getRemainderConfirmTime(videoConsult.getConsultTime(), videoConsult.getVideoConsultStatus()))
                            .pictureMessageFlg(Optional.ofNullable(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_IMAGE))
                                    .map(m -> 1).orElse(2))
                            .sendCaseFlg(Optional
                                    .ofNullable(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_DRCASE))
                                    .map(m -> 1).orElse(2))
                            .sendRecomFlg(Optional
                                    .ofNullable(this.getContent(messageMap, VideoConsultMessage.VIDEO_CONSULT_TYPE_TO_RECOM))
                                    .map(m -> 1).orElse(2))
                            .inquirerInfo(inquirer).conditionDesc(videoConsult.getConditionDesc())
                            .diagnosis(Optional.ofNullable(videoConsult.getDiagnosis()).map(d -> Arrays.asList(d.split(","))).orElse(null))
                            .offlineMedicalRecordUrl(this.getOfflineMedicalRecordUrl(inquirer.getIdCard(), inquirer.getName()))
                            .onlineMedicalRecordUrl(this.getOnlineMedicalRecordUrl(inquirer.getIdCard(), inquirer.getName()))
                            .refusalReason(videoConsult.getRefusalReason()).createdAt(videoConsult.getCreatedAt()).changedAt(videoConsult.getChangedAt())
                            .build();
                    GroupConsultListVO groupConsultListVO = GroupConsultListVO.builder().build();
                    cn.hutool.core.bean.BeanUtil.copyProperties(consultListVO, groupConsultListVO);
                    cn.hutool.core.bean.BeanUtil.copyProperties(videoGroupConsult, groupConsultListVO);
                    groupConsultListVO.setDiagnoseData(consultListVO.getDiagnosis());
                    groupConsultListVO.setPdfPath(Optional.ofNullable(videoGroupConsult.getPdfPath()).map(path -> imgHost + path).orElse(videoGroupConsult.getPdfPath()));
                    groupConsultListVO.setSourceHospitalName(basehospitalName);
                    return groupConsultListVO;
                }).collect(Collectors.toList())).build();
    }

    @Override
    public GroupConsultListVO groupConsultDetail(Long videoConsultId) throws BusinessException {
        DrVideoGroupConsult videoGroupConsult = groupConsultRepository.findFirstByVideoConsultId(videoConsultId);
        DrVideoConsult videoConsult = videoConsultRepository.get(videoConsultId);
        // 患者信息
        Patient patient = patientRepository.get(videoConsult.getPatientId());
        // 就诊人信息
        List<PatientInquirerBO> patientInquirers = apFeignClient.getPatientInquirer(Collections.singletonList(videoConsult.getInquirerId()));
        PatientInquirerBO inquirer = patientInquirers.get(0);
        DoctorConsultListVO consultListVO = DoctorConsultListVO.builder()
                .patientPhoto(patient.getHeadUrl())
                .patientName(patient.getName())
                .inquirerAgeStr(inquirer.getAge())
                .inquirerGender(inquirer.getGender())
                .inquirerName(inquirer.getName()).videoConsultId(videoConsult.getId())
                .patientId(videoConsult.getPatientId())
                .status(videoConsult.getVideoConsultStatus())
                .endTime(videoConsult.getChangedAt().getTime())
                .roomId(videoConsult.getRoomId()).inquirerId(videoConsult.getInquirerId())
                .talkTime(Optional.ofNullable(videoConsult.getTalkTime()).map(t -> DateUtil.secondToTime(t.intValue())).orElse("--:--:--"))
                .price(videoConsult.getPrice())
                .remainderConfirmTime(Optional.ofNullable(getRemainderConfirmTime(videoConsult.getConsultTime(), videoConsult.getVideoConsultStatus()))
                        .map(t -> DateUtil.secondToTime(t.intValue())).orElse(null))
                .remainderConfirmTimeSecond(getRemainderConfirmTime(videoConsult.getConsultTime(), videoConsult.getVideoConsultStatus()))
                .inquirerInfo(inquirer).conditionDesc(videoConsult.getConditionDesc())
                .offlineMedicalRecordUrl(this.getOfflineMedicalRecordUrl(inquirer.getIdCard(), inquirer.getName()))
                .onlineMedicalRecordUrl(this.getOnlineMedicalRecordUrl(inquirer.getIdCard(), inquirer.getName()))
                .refusalReason(videoConsult.getRefusalReason()).createdAt(videoConsult.getCreatedAt()).changedAt(videoConsult.getChangedAt())
                .build();
        GroupConsultListVO groupConsultListVO = GroupConsultListVO.builder().build();
        cn.hutool.core.bean.BeanUtil.copyProperties(consultListVO, groupConsultListVO);
        cn.hutool.core.bean.BeanUtil.copyProperties(videoGroupConsult, groupConsultListVO);
        groupConsultListVO.setDiagnoseData(consultListVO.getDiagnosis());
        groupConsultListVO.setPdfPath(Optional.ofNullable(videoGroupConsult.getPdfPath()).map(path -> imgHost + path).orElse(videoGroupConsult.getPdfPath()));
        groupConsultListVO.setSourceHospitalName(basehospitalName);
        return groupConsultListVO;
    }
}
